package com.jl.magic.utils;

import com.jl.exceptions.ImageDealException;
import com.jl.jtils.file.FileType;
import com.jl.magic.enums.ScaleEnum;
import com.jl.magic.images.BaseTools;

import javax.imageio.*;
import javax.imageio.plugins.jpeg.JPEGImageWriteParam;
import java.awt.*;
import java.awt.font.FontRenderContext;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Base64;
import java.util.Random;

public class ImageUtils {

    private static final Random RANDOM = new Random();

    public static String chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890";

    /**
     * 生成验证码图片
     *
     * @param target 生成的验证码图片
     */
    public static void generateSecurityCode(String target) throws ImageDealException {
        try {
            int width = 120;
            int height = 50;
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = image.getGraphics();
            g.setColor(new Color(random(50, 250), random(50, 250), random(50, 250)));
            g.fillRect(0, 0, width, height);
            for (int i = 0; i < 4; i++) {
                g.setColor(new Color(random(50, 180), random(50, 180), random(50, 180)));
                g.setFont(new Font("黑体", Font.PLAIN, 40));
                char c = chars.charAt(random(0, chars.length()));
                g.drawString(String.valueOf(c), 5 + i * 30, random(height - 20, height - 10));
            }
            //画随机线
            for (int i = 0; i < 15; i++) {
                g.setColor(new Color(random(50, 180), random(50, 180), random(50, 180)));
                g.drawLine(random(0, width), random(0, height), random(0, width), random(0, height));
            }
            ImageIO.write(image, "png", new File(target));
        } catch (Exception e) {
            throw new ImageDealException("生成验证码图片异常", e);
        }
    }

    private static int random(int min, int max) {
        return RANDOM.nextInt(max - min) + min;
    }

    /**
     * 给图片增加水印
     *
     * @param sourceImg 源图（待添加水印的图片）
     * @param targetImg 目标图片（添加水印后的图片）
     * @param pressImg  水印图片
     * @param x         水印在源图x坐标
     * @param y         水印在源图y坐标
     */
    public static void pressImage(String sourceImg, String targetImg, String pressImg, int x, int y) throws ImageDealException {
        try {
            BufferedImage src = ImageIO.read(new File(sourceImg));
            int width = src.getWidth();
            int height = src.getHeight();
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = image.createGraphics();
            g.drawImage(src, 0, 0, width, height, null);

            BufferedImage press = ImageIO.read(new File(pressImg));
            int pressWidth = press.getWidth();
            int pressHeight = press.getHeight();
            g.drawImage(press, x, y, pressWidth, pressHeight, null);
            g.dispose();

            ImageIO.write(image, "jpg", new File(targetImg));
        } catch (Exception e){
            throw new ImageDealException("给图片添加图片水印异常", e);
        }
    }

    /**
     * 添加文本水印
     *
     * @param sourceImg 源图片
     * @param targetImg 目标图片
     * @param text      待添加文本
     * @param fontName  字体
     * @param fontStyle 字体样式
     * @param fontSize  字体大小
     * @param color     颜色
     * @param x         文本水印x方向偏移量，如果x<0，则居中
     * @param y         文本水印y方向偏移量，如果y<0，则居中
     * @param alpha     文本水印透明度（0~1），0为完全透明，1为完全不透明
     */
    public static void pressText(String sourceImg, String targetImg, String text, String fontName, int fontStyle, int fontSize,
                                 Color color, int x, int y, float alpha) throws ImageDealException {
        try {
            BufferedImage src = ImageIO.read(new File(sourceImg));
            int width = src.getWidth(null);
            int height = src.getHeight(null);
            BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = image.createGraphics();
            g.drawImage(src, 0, 0, width, height, null);
            Font font = new Font(fontName, fontStyle, fontSize);
            g.setFont(font);
            g.setColor(color);
            g.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_ATOP, alpha));

            FontRenderContext fontRenderContext = g.getFontRenderContext();
            Rectangle2D stringBounds = font.getStringBounds(text, fontRenderContext);
            double textWidth = stringBounds.getWidth();
            double textHeight = stringBounds.getHeight();
            int posX, posY;
            if (x < 0) {
                posX = (int) ((width - textWidth) / 2);
            } else {
                posX = x;
            }

            if (y < 0) {
                posY = (int) ((height - textHeight) / 2);
            } else {
                posY = y;
            }

            g.drawString(text, posX, posY);
            g.dispose();
            ImageIO.write(image, "jpg", new File(targetImg));
        } catch (Exception e) {
            throw new ImageDealException("给图片添加文本水印异常", e);
        }
    }

    public static boolean isImage(File file) {
        if (file == null || !file.isFile()) {
            return false;
        }
        String name = file.getName().toLowerCase();
        // 常见的图片扩展名
        return name.endsWith(".jpg") ||
                name.endsWith(".jpeg") ||
                name.endsWith(".png") ||
                name.endsWith(".gif") ||
                name.endsWith(".bmp") ||
                name.endsWith(".webp") ||
                name.endsWith(".svg");
    }

    /**
     * 图片转base64字符串
     *
     * @param file 图片文件
     * @return base64字符串
     */
    public static String imgToBase64(String file) throws ImageDealException {
        return imgToBase64(new File(file));
    }

    /**
     * 图片转base64字符串
     *
     * @param file 图片文件
     * @return base64字符串
     */
    public static String imgToBase64(File file) throws ImageDealException {
        if (!isImage(file)) {
            return null;
        }
        try {
            return imgToBase64(new FileInputStream(file));
        } catch (Exception e) {
            throw new ImageDealException("图片转base64字符串异常", e);
        }
    }

    /**
     * 图片转base64字符串
     *
     * @param is 文件输入流
     * @return base64字符串
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    public static String imgToBase64(FileInputStream is) throws ImageDealException {
        try {
            byte[] data = new byte[is.available()];
            is.read(data);
            is.close();
            return Base64.getEncoder().encodeToString(data);
        } catch (Exception e) {
            throw new ImageDealException("图片转base64字符串异常", e);
        }
    }

    /**
     * base64字符串转图片
     *
     * @param imgStr     base64字符串
     * @param targetFile 目标图片
     * @return 目标图片是否生成成功
     */
    public static boolean base64ToImg(String imgStr, String targetFile) throws ImageDealException {
        if (imgStr == null) {
            return false;
        }
        try {
            byte[] decode = Base64.getDecoder().decode(imgStr);
            OutputStream os = Files.newOutputStream(Paths.get(targetFile));
            os.write(decode);
            os.flush();
            os.close();
            return true;
        } catch (Exception e) {
            throw new ImageDealException("base64字符串转图片异常", e);
        }
    }

    /**
     * 提取
     *
     * @param image BufferedImage
     * @return BufferedImage
     */
    public static BufferedImage extract(BufferedImage image) {
        int width = image.getWidth();
        int height = image.getHeight();
        BufferedImage bfi = new BufferedImage(width, height, BufferedImage.TYPE_4BYTE_ABGR);
        Graphics graphics = image.getGraphics();
        graphics.drawImage(image, 0, 0, null);
        for (int y = 0; y < height; y++) {
            for (int x = 0; x < width; x++) {
                int pixel = image.getRGB(x, y);
                int[] rgb = BaseTools.rgb(pixel);
                int bright = (int) (0.21f * rgb[0] + 0.71f * rgb[1] + 0.08f * rgb[2]);
                pixel = (bright << 24) & 0xff000000 | (pixel & 0x00ffffff);
                bfi.setRGB(x, y, pixel);
            }
        }
        return bfi;
    }

    /**
     * 按图片短边1:1居中裁剪图片
     *
     * @param bfi 源image
     * @return 返回处理后的image
     */
    public static BufferedImage tailor(BufferedImage bfi) {
        int height = bfi.getHeight();
        int width = bfi.getWidth();
        int min = Math.min(width, height);
        //居中裁剪
        int half, x, y;
        if (min == width) {
            half = (height - min) / 2;
            x = 0;
            y = half;
        } else {
            half = (width - min) / 2;
            x = half;
            y = 0;
        }
        BufferedImage ret = bfi.getSubimage(x, y, min, min);
        BufferedImage target = new BufferedImage(min, min, ret.getType());
        Graphics graphics = target.getGraphics();
        graphics.drawImage(ret, 0, 0, null);
        graphics.dispose();
        return target;
    }

    /**
     * 按比例进行缩放图片
     *
     * @param bfi       图片
     * @param rate      缩放比例
     * @param scaleEnum 缩放方式
     * @return 返回缩放后的image
     */
    public static BufferedImage scale(BufferedImage bfi, float rate, ScaleEnum scaleEnum) throws ImageDealException {
        int type = bfi.getType();
        if (type == BufferedImage.TYPE_CUSTOM) {
            type = BufferedImage.TYPE_3BYTE_BGR;
        }

        int width = bfi.getWidth();
        int height = bfi.getHeight();

        int newWidth = (int) (width * rate);
        int newHeight = (int) (height * rate);

        BufferedImage bufImage;
        Image image;

        switch (scaleEnum) {
            case DEFAULT:
            case FAST:
            case SMOOTH:
            case REPLICATE:
            case AREA_AVERAGING:
                bufImage = new BufferedImage(newWidth, newHeight, type);
                image = bfi.getScaledInstance(newWidth, newHeight, scaleEnum.getType());
                Graphics graphics = bufImage.getGraphics();
                graphics.drawImage(image, 0, 0, null);
                graphics.dispose();
                return bufImage;
            case AREA_AVERAGING_2D:
                bufImage = new BufferedImage(newWidth, newHeight, type);
                Graphics2D g2d = bufImage.createGraphics();
                bufImage = g2d.getDeviceConfiguration().createCompatibleImage(newWidth, newHeight, Transparency.OPAQUE);
                g2d.dispose();
                g2d = bufImage.createGraphics();
                image = bfi.getScaledInstance(newWidth, newHeight, Image.SCALE_AREA_AVERAGING);
                g2d.drawImage(image, 0, 0, null);
                g2d.dispose();
                return bufImage;
            default:
                throw new ImageDealException("不支持的缩放类型");
        }
    }


    /**
     * 压缩图片
     *
     * @param bfi 待压缩图片
     * @return 压缩后的图片
     */
    public static BufferedImage compress(BufferedImage bfi) throws ImageDealException {
        //指定写图片的方式为jpg
        ImageWriter imageWriter = ImageIO.getImageWritersByFormatName(FileType.JPG.getExtension().toLowerCase()).next();
        ImageWriteParam imageWriteParam = new JPEGImageWriteParam(null);
        //执行压缩模式
        imageWriteParam.setCompressionMode(ImageWriteParam.MODE_EXPLICIT);
        //指定压缩程度，取值范围0~1
        imageWriteParam.setCompressionQuality(0.2f);
        imageWriteParam.setProgressiveMode(ImageWriteParam.MODE_DISABLED);
        //指定压缩时使用的色彩模式
        ColorModel colorModel = bfi.getColorModel();
        ImageTypeSpecifier imageTypeSpecifier = new ImageTypeSpecifier(colorModel, colorModel.createCompatibleSampleModel(16, 16));
        imageWriteParam.setDestinationType(imageTypeSpecifier);
        try (ByteArrayOutputStream os = new ByteArrayOutputStream()) {
            imageWriter.reset();
            //必须先执行out，才能write
            imageWriter.setOutput(ImageIO.createImageOutputStream(os));
            IIOImage iioImage = new IIOImage(bfi, null, null);
            //调用write向out流写入数据
            imageWriter.write(null, iioImage, imageWriteParam);
            //将out流转换成bufferedImage
            byte[] bytes = os.toByteArray();
            InputStream is = new ByteArrayInputStream(bytes);
            BufferedImage read = ImageIO.read(is);
            is.close();
            return read;
        } catch (Exception e) {
            throw new ImageDealException("图片压缩处理异常", e);
        }
    }

}
